// Package gojs allows you to run wasm binaries compiled by Go when `GOOS=js`
// and `GOARCH=wasm`. See https://wazero.io/languages/go/ for more.
//
// # Experimental
//
// Go defines js "EXPERIMENTAL... exempt from the Go compatibility promise."
// Accordingly, wazero cannot guarantee this will work from release to release,
// or that usage will be relatively free of bugs. Due to this and the
// relatively high implementation overhead, most will choose TinyGo instead.
package gojs

import (
	"context"
	"net/http"

	"wa-lang.org/wazero"
	. "wa-lang.org/wazero/internal/gojs"
	"wa-lang.org/wazero/internal/wasm"
)

// WithRoundTripper sets the http.RoundTripper used to Run Wasm.
//
// For example, if the code compiled via `GOARCH=wasm GOOS=js` uses
// http.RoundTripper, you can avoid failures by assigning an implementation
// like so:
//
//	ctx = gojs.WithRoundTripper(ctx, http.DefaultTransport)
//	err = gojs.Run(ctx, r, compiled, config)
func WithRoundTripper(ctx context.Context, rt http.RoundTripper) context.Context {
	return context.WithValue(ctx, RoundTripperKey{}, rt)
}

// Run instantiates a new module and calls "run" with the given config.
//
// # Parameters
//
//   - ctx: context to use when instantiating the module and calling "run".
//   - r: runtime to instantiate both the host and guest (compiled) module in.
//   - compiled: guest binary compiled with `GOARCH=wasm GOOS=js`
//   - config: the configuration such as args, env or filesystem to use.
//
// # Example
//
// After compiling your Wasm binary with wazero.Runtime's `CompileModule`, run
// it like below:
//
//	// Use compilation cache to reduce performance penalty of multiple runs.
//	ctx = experimental.WithCompilationCacheDirName(ctx, ".build")
//	// Execute the "run" function, which corresponds to "main" in stars/main.go.
//	err = gojs.Run(ctx, r, compiled, config)
//	if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
//		log.Panicln(err)
//	} else if !ok {
//		log.Panicln(err)
//	}
//
// # Notes
//
//   - Use wazero.RuntimeConfig `WithWasmCore2` to avoid needing to pick >1.0
//     features set by `GOWASM` or used internally by Run.
//   - Wasm generated by `GOARCH=wasm GOOS=js` is very slow to compile.
//     Use experimental.WithCompilationCacheDirName to improve performance.
//   - Both the host and guest module are closed after being run.
func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, config wazero.ModuleConfig) error {
	// Instantiate the imports needed by go-compiled wasm.
	js, err := hostModuleBuilder(r).Instantiate(ctx, r)
	if err != nil {
		return err
	}
	defer js.Close(ctx)

	// Instantiate the module compiled by go, noting it has no init function.
	mod, err := r.InstantiateModule(ctx, compiled, config)
	if err != nil {
		return err
	}
	defer mod.Close(ctx)

	// Extract the args and env from the module config and write it to memory.
	ctx = WithState(ctx)
	argc, argv, err := WriteArgsAndEnviron(ctx, mod)
	if err != nil {
		return err
	}
	// Invoke the run function.
	_, err = mod.ExportedFunction("run").Call(ctx, uint64(argc), uint64(argv))
	return err
}

// hostModuleBuilder returns a new wazero.HostModuleBuilder
func hostModuleBuilder(r wazero.Runtime) (builder wazero.HostModuleBuilder) {
	builder = r.NewHostModuleBuilder("go")
	hfExporter := builder.(wasm.HostFuncExporter)
	pfExporter := builder.(wasm.ProxyFuncExporter)

	pfExporter.ExportProxyFunc(GetRandomData)
	pfExporter.ExportProxyFunc(Nanotime1)
	pfExporter.ExportProxyFunc(WasmExit)
	pfExporter.ExportProxyFunc(CopyBytesToJS)
	pfExporter.ExportProxyFunc(ValueCall)
	pfExporter.ExportProxyFunc(ValueGet)
	pfExporter.ExportProxyFunc(ValueIndex)
	pfExporter.ExportProxyFunc(ValueLength)
	pfExporter.ExportProxyFunc(ValueNew)
	pfExporter.ExportProxyFunc(ValueSet)
	pfExporter.ExportProxyFunc(WasmWrite)
	hfExporter.ExportHostFunc(ResetMemoryDataView)
	pfExporter.ExportProxyFunc(Walltime)
	hfExporter.ExportHostFunc(ScheduleTimeoutEvent)
	hfExporter.ExportHostFunc(ClearTimeoutEvent)
	pfExporter.ExportProxyFunc(FinalizeRef)
	pfExporter.ExportProxyFunc(StringVal)
	hfExporter.ExportHostFunc(ValueDelete)
	hfExporter.ExportHostFunc(ValueSetIndex)
	hfExporter.ExportHostFunc(ValueInvoke)
	pfExporter.ExportProxyFunc(ValuePrepareString)
	hfExporter.ExportHostFunc(ValueInstanceOf)
	pfExporter.ExportProxyFunc(ValueLoadString)
	pfExporter.ExportProxyFunc(CopyBytesToGo)
	hfExporter.ExportHostFunc(Debug)
	return
}
